
#include <random>
#include <QtOpenGL/QtOpenGL>
#include <QtCore/QSharedPointer>
#include "QWidgetsContainer.h"




#pragma region QTransition

#define QTRANSITIONMODE QWidgetsContainer::TRANSITION_MODE
#define C_MODE(MODE) QTransitionT<MODE>
#define C_MODE_BEGIN(MODE ) template<> class QTransitionT<MODE> :public QTransition{	public:\
	QTransitionT(QWidgetsContainer* wc, QObject* parent=0):QTransition(wc, parent)

#define C_MODE_END  };

class QTransition :public QObject
{
public:
	QTransition(QWidgetsContainer* wc, QObject* parent = 0)
		:QObject(parent), _wc(wc)
	{}
	virtual ~QTransition() {}
	static std::unique_ptr<QTransition> create(QWidgetsContainer* wc, QTRANSITIONMODE mode);

	virtual void valueChanged(qreal value) = 0;
	virtual void finished() {};

	QWidgetsContainer* _wc;
};

template<QTRANSITIONMODE MODE>
class QTransitionT;


//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_ROTATE_Y_CCW){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget && value > 0.5)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;
		_wc->_oldWidget->setVisible(false);
		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);

	}
	auto s = value > 0.5 ? value : 1 - value;
	s *= s;
	auto r = _wc->_currentWidget->boundingRect();
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(r.width() / 2, r.height() / 2)
									  .scale(s, s)
									  .rotate(value * 180 - 180 * static_cast<int>(value > 0.5), Qt::YAxis)
									  .translate(-r.width() / 2, -r.height() / 2));
}

C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_ROTATE_Y_CW){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget && value > 0.5)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;
		_wc->_oldWidget->setVisible(false);
		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);

	}
	auto r = _wc->_currentWidget->boundingRect();
	auto s = value > 0.5 ? value : 1 - value;
	s *= s;
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(r.width() / 2, r.height() / 2)
									  .scale(s, s)
									  .rotate(180 * static_cast<int>(value > 0.5) - value * 180, Qt::YAxis)
									  .translate(-r.width() / 2, -r.height() / 2));

}

C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_ROTATE_X_CCW){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget && value > 0.5)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;
		_wc->_oldWidget->setVisible(false);
		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);

	}
	auto r = _wc->_currentWidget->boundingRect();
	auto s = value > 0.5 ? value : 1 - value;
	s *= s;
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(r.width() / 2, r.height() / 2)
									  .scale(s, s)
									  .rotate(180 * static_cast<int>(value > 0.5) - value * 180, Qt::XAxis)
									  .translate(-r.width() / 2, -r.height() / 2));

}
C_MODE_END


//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_ROTATE_X_CW){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget && value > 0.5)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;
		_wc->_oldWidget->setVisible(false);
		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);

	}
	auto r = _wc->_currentWidget->boundingRect();
	auto s = value > 0.5 ? value : 1 - value;
	s *= s;
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(r.width() / 2, r.height() / 2)
									  .scale(s, s)
									  .rotate(value * 180 - 180 * static_cast<int>(value > 0.5), Qt::XAxis)
									  .translate(-r.width() / 2, -r.height() / 2));

}

C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_BOX_H_LEFT){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;

		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);
	}


	auto r = _wc->_oldWidget->boundingRect();


	QMatrix4x4 mt;

	mt.translate(r.width() / 2, r.height() / 2, r.width() / 2);
	mt.rotate(value * 90, 0, 1);
	
	mt.translate(-r.width() / 2, -r.height() / 2, -r.width() / 2);

	_wc->_oldWidget->setTransform(mt.toTransform(-1024));

	r = _wc->_currentWidget->boundingRect();

	mt.setToIdentity();
	mt.translate(r.width() / 2, r.height() / 2, r.width() / 2);
	mt.rotate((value - 1) * 90, 0, 1);
	mt.translate(-r.width() / 2, -r.height() / 2, -r.width() / 2);

	_wc->_currentWidget->setTransform(mt.toTransform(-1024));

}
C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_TRANSLATE_X_LEFT){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;

		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);
	}
	auto r = _wc->_oldWidget->boundingRect();
	_wc->_oldWidget->setTransform(QTransform()
								  .translate(r.width() * -value, 0)
								  );
	r = _wc->_currentWidget->boundingRect();
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(r.width()*(1 - value), 0)
									  );

}
C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_TRANSLATE_X_RIGHT){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;

		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);
	}
	auto r = _wc->_oldWidget->boundingRect();
	_wc->_oldWidget->setTransform(QTransform()
								  .translate(r.width() * value, 0)
								  );
	r = _wc->_currentWidget->boundingRect();
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(r.width()*(value - 1), 0)
									  );
}
C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_TRANSLATE_Y_UP){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;

		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);
	}
	auto r = _wc->_oldWidget->boundingRect();
	_wc->_oldWidget->setTransform(QTransform()
								  .translate(0, r.height()* -value)
								  );
	r = _wc->_currentWidget->boundingRect();
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(0, r.height()*(1 - value))
									  );
}
C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_TRANSLATE_Y_DOWN){}
void valueChanged(qreal value)
{
	if(_wc->_targetWidget)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;

		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(true);
	}
	auto r = _wc->_oldWidget->boundingRect();
	_wc->_oldWidget->setTransform(QTransform()
								  .translate(0, r.height()* value)
								  );
	r = _wc->_currentWidget->boundingRect();
	_wc->_currentWidget->setTransform(QTransform()
									  .translate(0, r.height()*(value - 1))
									  );
}
C_MODE_END



//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_SHADE_X){}
QImage _imgOld, _imgCurrent;
QList<QGraphicsPixmapItem*> _imgOldItem, _imgCurrentItem;
void valueChanged(qreal value)
{
	const int ROW = 10, COL = 15;
	if(_wc->_targetWidget)
	{
		_wc->_oldWidget = _wc->_currentWidget;
		_wc->_currentWidget = _wc->_targetWidget;
		_wc->_targetWidget = 0;
		_imgOld = _wc->renderWidget(_wc->_oldWidget);
		_imgCurrent = _wc->renderWidget(_wc->_currentWidget);

		_imgOldItem.clear();
		_imgCurrentItem.clear();

		float w = (float)_wc->width() / COL;
		float h = (float)_wc->height() / ROW;
		//old
		{
			for(int r = 0; r < ROW; r++)
			{
				for(int c = 0; c < COL; c++)
				{
					QPixmap pixmap(w, h);
					QPainter p(&pixmap);
					p.drawImage(0, 0, _imgOld, c*w, r*h, w, h);
					auto pm = _wc->_scene.addPixmap(pixmap);
					pm->setPos(c*w, r*h);
					pm->setVisible(false);
					_imgOldItem.push_back(pm);
				}
			}
		}
		// current
		{
			for(int r = 0; r < ROW; r++)
			{
				for(int c = 0; c < COL; c++)
				{
					QPixmap pixmap(w, h);
					QPainter p(&pixmap);

					p.drawImage(0, 0, _imgCurrent, c*w, r*h, w, h);

					auto pm = _wc->_scene.addPixmap(pixmap);
					pm->setVisible(false);
					pm->setPos(c*w, r*h);
					_imgCurrentItem.push_back(pm);
				}
			}
		}
		_wc->_currentWidget->setGeometry(_wc->rect());
		_wc->_currentWidget->setVisible(false);
	}

	if(value < 0.5)
	{
		int i = 0;
		for(auto pm : _imgOldItem)
		{
			auto f = (float)i / (ROW*COL);

			if(value<f / 2)
			{
				value = 0;
			}
			pm->setVisible(true);
			auto r = pm->boundingRect();
			pm->setTransform(QTransform()
							 .translate(r.width() / 2, r.height() / 2)
							 .scale(1 - value, 1 - value)
							 .rotate(value * 180 - 180 * static_cast<int>(value > 0.5), Qt::YAxis)
							 .translate(-r.width() / 2, -r.height() / 2)
							 );
			i++;
		}

		for(auto pm : _imgCurrentItem)
		{
			pm->setVisible(false);
		}
	}
	else
	{
		for(auto pm : _imgOldItem)
		{
			pm->setVisible(false);

		}
		int i = 0;
		for(auto pm : _imgCurrentItem)
		{
			auto f = (float)i / (ROW*COL);
			if(value - 0.5<f / 2)
			{
				value = 0.5;
			}
			pm->setVisible(true);
			auto r = pm->boundingRect();
			pm->setTransform(QTransform()
							 .translate(r.width() / 2, r.height() / 2)
							 .scale(value, value)
							 .rotate(value * 180 - 180 * static_cast<int>(value > 0.5), Qt::YAxis)
							 .translate(-r.width() / 2, -r.height() / 2)
							 );
			i++;
		}

	}

}
void finished()
{
	for(auto pm : _imgOldItem)
	{
		_wc->_scene.removeItem(pm);
		delete pm;
	}
	for(auto pm : _imgCurrentItem)
	{
		_wc->_scene.removeItem(pm);
		delete pm;
	}
	_imgOldItem.clear();
	_imgCurrentItem.clear();
}
C_MODE_END

//////////////////////////////////////////////////////////////////////////
C_MODE_BEGIN(QTRANSITIONMODE::MODE_RANDOM)
{
	QTRANSITIONMODE mode = static_cast<QTRANSITIONMODE>(std::random_device()() % QTRANSITIONMODE::MODE_RANDOM);
	_t = QTransition::create(wc, mode);
}
void valueChanged(qreal value)
{
	_t->valueChanged(value);
}
void finished()
{
	_t->finished();
}
std::unique_ptr<QTransition> _t;
C_MODE_END


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
std::unique_ptr<QTransition> QTransition::create(QWidgetsContainer* wc, QTRANSITIONMODE mode)
{

#define CASE(MODE)  case (MODE) : return std::make_unique< C_MODE(MODE)> (wc);break;

	switch(mode)
	{
		CASE(QTRANSITIONMODE::MODE_ROTATE_Y_CCW);
		CASE(QTRANSITIONMODE::MODE_ROTATE_Y_CW);
		CASE(QTRANSITIONMODE::MODE_ROTATE_X_CCW);
		CASE(QTRANSITIONMODE::MODE_ROTATE_X_CW);

		CASE(QTRANSITIONMODE::MODE_BOX_H_LEFT);

		CASE(QTRANSITIONMODE::MODE_TRANSLATE_X_LEFT);
		CASE(QTRANSITIONMODE::MODE_TRANSLATE_X_RIGHT);
		CASE(QTRANSITIONMODE::MODE_TRANSLATE_Y_UP);
		CASE(QTRANSITIONMODE::MODE_TRANSLATE_Y_DOWN);

		CASE(QTRANSITIONMODE::MODE_SHADE_X);
		CASE(QTRANSITIONMODE::MODE_RANDOM);
		default:
			break;
	}
	return nullptr;
}

#pragma endregion





QWidgetsContainer::QWidgetsContainer(QWidget *parent)
: QGraphicsView(parent)
, _currentWidget(0)
, _targetWidget(0)
, _oldWidget(0)
{

	this->setScene(&_scene);
	this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

	this->setBackgroundBrush(QBrush(Qt::darkGray));

	//this->setWindowFlags(Qt::WindowType::FramelessWindowHint);
	//this->setAttribute(Qt::WA_TranslucentBackground);
	//this->setStyleSheet(R"(border:0px ;background-color:transparent;)");

	connect(&_timeline, &QTimeLine::valueChanged, this, &QWidgetsContainer::valueChanged);
	connect(&_timeline, &QTimeLine::finished, this, &QWidgetsContainer::finished);

}

QWidgetsContainer::~QWidgetsContainer()
{

}

void QWidgetsContainer::resizeEvent(QResizeEvent *e)
{
	QGraphicsView::resizeEvent(e);
	if(scene())
	{
		scene()->setSceneRect(this->rect());
	}
	if(_currentWidget)
	{
		_currentWidget->resize(e->size());
	}
}

QGraphicsProxyWidget * QWidgetsContainer::addWidget(QWidget *widget, Qt::WindowFlags wFlags /*= 0*/)
{
	if(_widgets.find(widget) != _widgets.end())
	{
		return _widgets[widget];
	}
	auto w = _scene.addWidget(widget, wFlags);
	//w->setCacheMode(QGraphicsItem::ItemCoordinateCache);
	w->resize(this->size());
	w->setVisible(false);
	_widgets.emplace(widget, w);
	return w;
}

QGraphicsProxyWidget * QWidgetsContainer::removeWidget(QWidget *widget, bool destory)
{
	if(_widgets.find(widget) != _widgets.end())
	{
		auto w = _widgets[widget];
		_widgets.erase(widget);
		_scene.removeItem(w);
		if(destory)
		{
			delete w;
			w = 0;
		}
		return w;
	}
	return 0;
}



void QWidgetsContainer::setCurrent(QWidget* widget)
{
	if(_widgets.find(widget) != _widgets.end())
	{
		if(_timeline.state() == QTimeLine::Running)
		{
			_timeline.stop();
		}
		auto w = _widgets[widget];
		_oldWidget = _currentWidget;
		_currentWidget = w;
		_targetWidget = 0;
		if(_oldWidget)
		{
			_oldWidget->setVisible(false);
		}

		_currentWidget->setGeometry(this->rect());

		_currentWidget->setVisible(true);
		_currentWidget->setTransform(QTransform());
	}
}

void QWidgetsContainer::transition(QWidget* targetWidget, float time, TRANSITION_MODE mode)
{
	if(!_currentWidget || _currentWidget->widget() == targetWidget || _timeline.state() == QTimeLine::Running)
	{
		return;
	}
	_mode = mode;
	_targetWidget = _widgets[targetWidget];
	_targetWidget->setZValue(0);
	_currentWidget->setZValue(1);

	_transition = QTransition::create(this, _mode);
	_timeline.setDuration(time);
	_timeline.setFrameRange(0, 180);
	_timeline.setUpdateInterval(15);
	_timeline.start();
}

void QWidgetsContainer::valueChanged(qreal value)
{
	_transition->valueChanged(value);
}

void QWidgetsContainer::finished()
{
	_transition->finished();

	_oldWidget->setVisible(false);
	_oldWidget->setZValue(0);
	_currentWidget->setVisible(true);
	_currentWidget->setTransform(QTransform());
	_currentWidget->setZValue(1);

	_oldWidget = 0;
	_targetWidget = 0;
}

std::map<QWidget*, QGraphicsProxyWidget*> const& QWidgetsContainer::getWidgets() const
{
	return _widgets;
}

QImage QWidgetsContainer::renderWidget(QGraphicsProxyWidget* w)
{
	Q_ASSERT(w->scene() == &_scene);
	for(auto&& k : _widgets)
	{
		k.second->setVisible(false);
	}
	w->setTransform(QTransform());
	w->setGeometry(this->rect());
	w->setVisible(true);
	_scene.update();
	QImage img{ width(), height(), QImage::Format::Format_RGBA8888 };
	{
		QPainter p(&img);
		_scene.render(&p);
	}
	w->setVisible(false);
	return img;
}
